package com.foreveross.framework.web.tags;

import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateNotFoundException;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.support.RequestContext;
import org.springframework.web.servlet.tags.form.TagWriter;

import javax.servlet.jsp.JspException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;

public class SelectTag extends org.springframework.web.servlet.tags.form.SelectTag {

  private String dataClass;

  private String dataMethod;

  private boolean hidden;

  @Override
  protected int writeTagContent(TagWriter tagWriter) throws JspException {
    if (hidden) {
      return SKIP_BODY;
    }

    logger.info("dataClass: " + dataClass);
    logger.info("dataMethod: " + dataMethod);

    if (!Strings.isNullOrEmpty(dataClass) && !Strings.isNullOrEmpty(dataMethod)) {
      try {
        Object dataClassInstance = getWebApplicationContext().getBean(Class.forName(dataClass));
        Method method = ReflectionUtils.findMethod(Class.forName(dataClass), dataMethod);
        Object result = ReflectionUtils.invokeMethod(method, dataClassInstance);
        setItems(result);
      } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
      }
    }

    try {
      Template template = getTemplate("select.ftl");
      template.process(getDataModel(), pageContext.getOut());
    } catch (IOException e) {
      logger.error(e.getMessage(), e);
      throw new RuntimeException(e);
    } catch (TemplateException e) {
      logger.error(e.getMessage(), e);
      throw new RuntimeException(e);
    }

    return SKIP_BODY;
  }

  @Override
  public int doEndTag() throws JspException {
    if (hidden) {
      return EVAL_PAGE;
    }

    try {
      Template template = getTemplate("select-end.ftl");
      template.process(getDataModel(), pageContext.getOut());
    } catch (TemplateNotFoundException e) {
      logger.info(e.getMessage(), e);
      // ignore
    } catch (IOException e) {
      logger.error(e.getMessage(), e);
      throw new RuntimeException(e);
    } catch (TemplateException e) {
      logger.error(e.getMessage(), e);
      throw new RuntimeException(e);
    }

    return super.doEndTag();
  }

  private Template getTemplate(String name) throws IOException {
    return getFreemarkerConfiguration().getTemplate(name);
  }

  private Configuration getFreemarkerConfiguration() {
    return getWebApplicationContext().getBean(Configuration.class);
  }

  private WebApplicationContext getWebApplicationContext() {
    RequestContext requestContext = getRequestContext();
    return requestContext.getWebApplicationContext();
  }

  private Map<String, Object> getDataModel() throws JspException {
    Map<String, Object> dataModel = Maps.newHashMap();

    dataModel.put("hidden", isHidden());

    // Select element
    dataModel.put("items", evaluate("items", getItems()));
    dataModel.put("itemValue", getItemValue() != null ?
        ObjectUtils.getDisplayString(evaluate("itemValue", getItemValue())) : null);
    dataModel.put("itemLabel", getItemLabel() != null ?
        ObjectUtils.getDisplayString(evaluate("itemLabel", getItemLabel())) : null);
    dataModel.put("size", getSize());
    dataModel.put("multiple", isMultiple());
    dataModel.put("boundValue", getBoundValue());

    // HTML input element
    dataModel.put("onfocus", getOnfocus());
    dataModel.put("onblur", getOnblur());
    dataModel.put("onchange", getOnchange());
    dataModel.put("accesskey", getAccesskey());
    dataModel.put("disabled", isDisabled());
    dataModel.put("readonly", isReadonly());

    // HTML element
    dataModel.put("class", resolveCssClass());
    dataModel.put("style", ObjectUtils.getDisplayString(evaluate("cssStyle", getCssStyle())));
    dataModel.put("lang", getLang());
    dataModel.put("title", getTitle());
    dataModel.put("dir", getDir());
    dataModel.put("tabindex", getTabindex());
    dataModel.put("onclick", getOnclick());
    dataModel.put("ondblclick", getOndblclick());
    dataModel.put("onmousedown", getOnmousedown());
    dataModel.put("onmouseup", getOnmouseup());
    dataModel.put("onmouseover", getOnmouseover());
    dataModel.put("onmousemove", getOnmousemove());
    dataModel.put("onmouseout", getOnmouseout());
    dataModel.put("onkeypress", getOnkeypress());
    dataModel.put("onkeyup", getOnkeyup());
    dataModel.put("onkeydown", getOnkeydown());
    dataModel.put("dynamicAttributes",
        MoreObjects.firstNonNull(getDynamicAttributes(), Maps.newHashMap()));

    dataModel.put("id", resolveId());
    dataModel.put("name", getName());
    dataModel.put("path", getPath());

    return dataModel;
  }

  private boolean isMultiple() throws JspException {
    Object multiple = getMultiple();
    if (multiple != null) {
      String stringValue = multiple.toString();
      return ("multiple".equalsIgnoreCase(stringValue) || Boolean.parseBoolean(stringValue));
    }
    return false;
  }

  public String getDataClass() {
    return dataClass;
  }

  public void setDataClass(String dataClass) {
    this.dataClass = dataClass;
  }

  public String getDataMethod() {
    return dataMethod;
  }

  public void setDataMethod(String dataMethod) {
    this.dataMethod = dataMethod;
  }

  public boolean isHidden() {
    return hidden;
  }

  public void setHidden(boolean hidden) {
    this.hidden = hidden;
  }

}
